﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace Exentials.Xoox.Spreadsheet
{
    [DebuggerDisplay("{ref}")]
    public class Reference
    {
        public const int MaxRow = 1048576;
        public const int MaxColumn = 16384;

        struct Ref
        {
            private static Regex regex = new Regex(@"^(?<colLock>\$?)(?<col>[A-Z]+)(?<rowLock>\$?)(?<row>[1-9][0-9]*)\b", RegexOptions.Compiled | RegexOptions.CultureInvariant);

            private int _row;
            private int _col;
            private bool _rowLock;
            private bool _colLock;
            private string _ref;

            public Ref(int row, int column)
            {
                if ((row < 1) || (row > MaxRow)) throw new ReferenceException("Row reference out of range: {0}", row);
                if ((column < 1) || (column > MaxColumn)) throw new ReferenceException("Column reference out of range: {0}", column);

                this._row = row;
                this._col = column;

                string reference = "";
                do
                {
                    column--;
                    reference = (char)(65 + column % 26) + reference;
                    column = column / 26;
                } while (column > 0);
                this._ref = reference + this._row.ToString(System.Globalization.CultureInfo.InvariantCulture);
                this._colLock = false;
                this._rowLock = false;
            }

            public Ref(string reference)
            {
                Match match = Ref.regex.Match(reference.ToUpperInvariant());
                if (!match.Success) throw new ReferenceException("Invalid reference");
                this._ref = reference;

                this._rowLock = !string.IsNullOrEmpty(match.Groups["rowLock"].Value);
                this._row = Convert.ToInt32(match.Groups["row"].Value);

                this._colLock = !string.IsNullOrEmpty(match.Groups["colLock"].Value);

                char[] chars = match.Groups["col"].Value.ToCharArray();
                Array.Reverse(chars);

                this._col = 0;

                int len = chars.Length;
                for (int i = 0; i < len; i++)
                {
                    this._col += (int)Math.Pow(26, i) * (chars[i] - 64);
                }

                if ((this._row < 1) || (this._row > MaxRow)) throw new ReferenceException("Row reference out of range: {0}", this._row);
                if ((this._col < 1) || (this._col > MaxColumn)) throw new ReferenceException("Column reference out of range: {0}", this._col);
            }

            public int Row
            {
                get { return _row; }
            }

            public bool IsRowLocked
            {
                get { return _rowLock; }
            }

            public int Column
            {
                get { return _col; }
            }

            public bool IsCloumnLocked
            {
                get { return _colLock; }
            }

            public string Value
            {
                get { return _ref; }
            }

            public override string ToString()
            {
                return _ref;
            }
        }

        private Ref _ref1;
        private Ref _ref2;
        private bool _singleReference;

        public Reference(int row, int column)
        {
            _ref1 = new Ref(row, column);
            _ref2 = _ref1;
            _singleReference = true;
        }

        public Reference(int startRow, int startColumn, int endRow, int endColumn)
        {
            _ref1 = new Ref(startRow, startColumn);
            _ref2 = new Ref(endRow, endColumn);
            _singleReference = false;
        }

        public Reference(string reference)
        {
            string[] r = reference.Split(new char[] { ':' });
            _ref1 = new Ref(r[0]);
            if (r.Length > 1)
            {
                _ref2 = new Ref(r[1]);
                _singleReference = false;
            }
            else
            {
                _ref2 = _ref1;
                _singleReference = true;
            }
        }

        public int Row
        {
            get { return _ref1.Row; }
        }

        public int Column
        {
            get { return _ref1.Column; }
        }

        public int RowBound
        {
            get { return _ref2.Row; }
        }

        public int ColumnBound
        {
            get { return _ref2.Column; }
        }

        public string Value
        {
            get { return _ref1.Value; }
        }

        public override string ToString()
        {
            return _singleReference ? _ref1.ToString() : string.Format("{0}:{1}", _ref1, _ref2);
        }

        public override bool Equals(object obj)
        {
            Reference cellReference = (Reference)obj;
            return (this._ref1.Row == cellReference._ref1.Row) && (this._ref1.Column == cellReference._ref1.Column);
        }

        public override int GetHashCode()
        {
            return _ref1.Row * Reference.MaxColumn + _ref1.Column;
        }

        public static bool operator ==(Reference cell1, Reference cell2)
        {
            return Reference.Equals(cell1, cell2);
        }

        public static bool operator !=(Reference cell1, Reference cell2)
        {
            return !Reference.Equals(cell1, cell2);
        }

        public static implicit operator Reference(string reference)
        {
            return new Reference(reference);
        }
    }
}

